package com.triangulator;

import java.util.ArrayList;
import java.util.List;
import java.util.StringTokenizer;

import com.ece594bb.project.localization_and_reidentification.R;
import com.triangulator.bluetooth.BluetoothChatService;
import com.triangulator.bluetooth.DeviceListActivity;
import com.triangulator.gps.LatLong;
import com.triangulator.gps.MyMapView;

import android.app.Activity;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.content.Context;
import android.content.Intent;
import android.graphics.Color;
import android.graphics.Rect;
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.location.Location;
import android.location.LocationListener;
import android.location.LocationManager;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.util.Log;
import android.view.Display;
import android.view.KeyEvent;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.Window;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;



public class Triangulator extends Activity implements SensorEventListener {
	private static String TAG="Triangulator";

	// Message types sent from the BluetoothChatService Handler
	public static final int MESSAGE_STATE_CHANGE = 1;
	public static final int MESSAGE_READ = 2;
	public static final int MESSAGE_WRITE = 3;
	public static final int MESSAGE_DEVICE_NAME = 4;
	public static final int MESSAGE_TOAST = 5;

	private LocationManager mLocationManager;
	private LocationListener mLocationListener;
	private static final int TWO_MINUTES = 1000 * 60 * 2;

	private SensorManager mSensorManager;
	protected Location currentLocation;
	public static List<LatLong> allMyLocations;
	public static List<LatLong> allOtherLocations;

	private float[] mGravs = new float[3];
	private float[] mGeoMags = new float[3];
	private float[] mOrientation = new float[3];
	private float[] mRotationM = new float[9];
	private float[] mRemapedRotationM = new float[9];

	protected float currentOrientation;

	// Key names received from the BluetoothChatService Handler
	public static final String DEVICE_NAME = "device_name";
	public static final String TOAST = "toast";

	// Intent request codes
	private static final int REQUEST_CONNECT_DEVICE = 1;
	private static final int REQUEST_ENABLE_BT = 2;
	private static final int RETURN_FROM_MAPS = 3;

	protected TextView mTitleLeft;
	protected TextView mTitleCenter;
	protected TextView mTitleRight;
	
	protected TextView mCountDownText;
	
	private boolean hasBtnBeenReleased = true;

	// Name of the connected device
	private String mConnectedDeviceName = null;
	// Array adapter for the conversation thread

	// Local Bluetooth adapter
	private BluetoothAdapter mBluetoothAdapter = null;
	protected BluetoothChatService mChatService = null;

	public static int width=0;
	public static int height=0;

	protected static View topPanel=null;
	protected static TopLevelView centerPanel=null;
	protected static View bottomPanel=null;
	protected static View centerCountDownPanel=null;
	
	protected static Button takePicture;
	protected static Button maps;
	protected static Button btn_restart;
	
	private Intent mapIntent;
	public static Toast spinningWheel;
	private Toast noConnection;

	public void onCreate(Bundle icicle) {
		super.onCreate(icicle);

		mapIntent=new Intent(Triangulator.this, MyMapView.class);

		Display display = getWindowManager().getDefaultDisplay(); 
		width = display.getWidth();
		height = display.getHeight();

		requestWindowFeature(Window.FEATURE_CUSTOM_TITLE);
		setContentView(R.layout.triangulator);
		getWindow().setFeatureInt(Window.FEATURE_CUSTOM_TITLE, R.layout.custom_title);

		// Set up the custom title
		mTitleLeft = (TextView) findViewById(R.id.title_left_text);
		mTitleCenter = (TextView) findViewById(R.id.title_center_text);
		mTitleRight = (TextView) findViewById(R.id.title_right_text);
		
		centerPanel= (TopLevelView) findViewById(R.id.topSurface);
		topPanel=findViewById(R.id.top_panel);
		bottomPanel=findViewById(R.id.bottom_panel);
		centerCountDownPanel=findViewById(R.id.center_panel_countdown);
		
		takePicture = (Button) findViewById(R.id.btn_takepicture);
		takePicture.setOnClickListener(new View.OnClickListener() {
			public void onClick(View arg0) {
				processLocation();
				hasBtnBeenReleased = true;
			}
		});
		
		maps = (Button) findViewById(R.id.btn_maps);
		maps.setOnClickListener(new View.OnClickListener() {
			public void onClick(View arg0) {
				startMaps(true);
			}
		});

		btn_restart = (Button) findViewById(R.id.btn_restart);
		btn_restart.setOnClickListener(new View.OnClickListener() {
			public void onClick(View arg0) {
				allMyLocations.clear();
				allOtherLocations.clear();
				updateCenterText();
			}
		});
		
		mCountDownText = (TextView) findViewById(R.id.countdown_text);
		
		// Get local Bluetooth adapter
		mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();

		// If the adapter is null, then Bluetooth is not supported
		if (mBluetoothAdapter == null) {
			Toast.makeText(this, "Bluetooth is not available", Toast.LENGTH_LONG).show();
			finish();
			return;
		}


		mLocationManager = (LocationManager)getSystemService(Context.LOCATION_SERVICE);

//		Location lastKnownNetLocation = mLocationManager.getLastKnownLocation(LocationManager.NETWORK_PROVIDER);
//		Location lastKnownGPSLocation = mLocationManager.getLastKnownLocation(LocationManager.GPS_PROVIDER);

		//		if (isBetterLocation(lastKnownNetLocation, lastKnownGPSLocation)) {
		//			Log.e("A", ""+lastKnownNetLocation);
		//		} else {
		//			Log.e("A", ""+lastKnownGPSLocation);
		//		}

		mLocationListener = new LocationListener() 
		{
			public void onLocationChanged(Location location) 
			{
				if (isBetterLocation(location, currentLocation)) {
					Log.v(TAG, "lat: "+location.getLatitude()+" long: "+location.getLongitude());

					currentLocation = location;
				}
			}

			public void onStatusChanged(String provider, int status, Bundle extras) {}
			public void onProviderEnabled(String provider) {}
			public void onProviderDisabled(String provider) {}
		};

		mSensorManager = (SensorManager)getSystemService(SENSOR_SERVICE);

		allMyLocations = new ArrayList<LatLong>();
		allOtherLocations = new ArrayList<LatLong>();

		spinningWheel = Toast.makeText(Triangulator.this, "Loading. Please wait..", Toast.LENGTH_SHORT);
		noConnection = Toast.makeText(Triangulator.this, R.string.no_gps_no_internet, Toast.LENGTH_SHORT);
		
		updateCenterText();
	}

	@Override
	public void onAccuracyChanged(Sensor sensor, int accuracy) {}

	@Override
	public void onSensorChanged(SensorEvent event) {
		switch (event.sensor.getType()) {
		case Sensor.TYPE_ACCELEROMETER:
			System.arraycopy(event.values, 0, mGravs, 0, 3);
			break;
		case Sensor.TYPE_MAGNETIC_FIELD:
			System.arraycopy(event.values, 0, mGeoMags, 0, 3);
			break;
		default:
			return;
		}


		if (SensorManager.getRotationMatrix(mRotationM, null, mGravs, mGeoMags)) {
			SensorManager.remapCoordinateSystem(mRotationM, SensorManager.AXIS_Z, SensorManager.AXIS_MINUS_X, mRemapedRotationM);

			SensorManager.getOrientation(mRemapedRotationM, mOrientation);

			currentOrientation=mOrientation[0];
		}
	}

	/** Determines whether one Location reading is better than the current Location fix
	 * @param location  The new Location that you want to evaluate
	 * @param currentBestLocation  The current Location fix, to which you want to compare the new one
	 */
	protected boolean isBetterLocation(Location location, Location currentBestLocation) {
		if (currentBestLocation == null) {
			// A new location is always better than no location
			return true;
		}

		// Check whether the new location fix is newer or older
		long timeDelta = location.getTime() - 
		currentBestLocation.getTime();
		boolean isSignificantlyNewer = timeDelta > TWO_MINUTES;
		boolean isSignificantlyOlder = timeDelta < -TWO_MINUTES;
		boolean isNewer = timeDelta > 0;

		// If it's been more than two minutes since the current location, use the new location
		// because the user has likely moved
		if (isSignificantlyNewer) {
			return true;
			// If the new location is more than two minutes older, it must be worse
		} else if (isSignificantlyOlder) {
			return false;
		}

		// Check whether the new location fix is more or less accurate
		int accuracyDelta = (int) (location.getAccuracy() - currentBestLocation.getAccuracy());
		boolean isLessAccurate = accuracyDelta > 0;
		boolean isMoreAccurate = accuracyDelta < 0;
		boolean isSignificantlyLessAccurate = accuracyDelta > 200;

		// Check if the old and new location are from the same provider
		boolean isFromSameProvider = isSameProvider(location.getProvider(),
				currentBestLocation.getProvider());

		// Determine location quality using a combination of timeliness and accuracy
		if (isMoreAccurate) {
			return true;
		} else if (isNewer && !isLessAccurate) {
			return true;
		} else if (isNewer && !isSignificantlyLessAccurate && isFromSameProvider) {
			return true;
		}
		return false;
	}

	/** Checks whether two providers are the same */
	private boolean isSameProvider(String provider1, String provider2) {
		if (provider1 == null)
			return provider2 == null;

		return provider1.equals(provider2);
	}


	@Override
	public void onStart() {
		super.onStart();

		Log.d(TAG, "++ ON START ++");

		// If BT is not on, request that it be enabled.
		// setupChat() will then be called during onActivityResult
		if (!mBluetoothAdapter.isEnabled()) {
			Intent enableIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
			startActivityForResult(enableIntent, REQUEST_ENABLE_BT);
			// Otherwise, setup the chat session
		} else {
			if (mChatService == null) setupChat();
		}
	}


	@Override
	public void onResume() {
		super.onResume();

		Log.d(TAG, "+ ON RESUME +");

		// Register the listener with the Location Manager to receive location updates
		mLocationManager.requestLocationUpdates(LocationManager.NETWORK_PROVIDER, 0, 0, mLocationListener);
		mLocationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 0, 0, mLocationListener);

		mSensorManager.registerListener(this, mSensorManager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD), SensorManager.SENSOR_DELAY_GAME);
		mSensorManager.registerListener(this, mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER), SensorManager.SENSOR_DELAY_GAME);


		if (mChatService != null) {
			// Only if the state is STATE_NONE, do we know that we haven't started already
			if (mChatService.getState() == BluetoothChatService.STATE_NONE) {
				// Start the Bluetooth chat services
				mChatService.start();
			}
		}
	}

	public int getTitleAndStatusHeight() {
		Rect rectgle= new Rect();
		Window window= getWindow();
		window.getDecorView().getWindowVisibleDisplayFrame(rectgle);
		int StatusBarHeight= rectgle.top;
		int contentViewTop= window.findViewById(Window.ID_ANDROID_CONTENT).getTop();
		int TitleBarHeight= contentViewTop - StatusBarHeight;

		Log.v(TAG, "StatusBar Height= " + StatusBarHeight + " , TitleBar Height = " + TitleBarHeight);

		return TitleBarHeight + StatusBarHeight;
	}

	private void setupChat() {
		Log.d(TAG, "setupChat()");

		// Initialize the BluetoothChatService to perform bluetooth connections
		mChatService = new BluetoothChatService(this, mHandler);
	}

	// The Handler that gets information back from the BluetoothChatService
	protected final Handler mHandler = new Handler() {
		@Override
		public void handleMessage(Message msg) {
			switch (msg.what) {
			case MESSAGE_STATE_CHANGE:

				Log.d(TAG, "MESSAGE_STATE_CHANGE: " + msg.arg1);

				switch (msg.arg1) {
				case BluetoothChatService.STATE_CONNECTED:
					mTitleRight.setTextColor(Color.GREEN);
					mTitleRight.setText(R.string.title_connected_to);
					mTitleRight.append(mConnectedDeviceName);
					break;
				case BluetoothChatService.STATE_CONNECTING:
					mTitleRight.setTextColor(Color.YELLOW);
					mTitleRight.setText(R.string.title_connecting);
					break;
				case BluetoothChatService.STATE_LISTEN:
				case BluetoothChatService.STATE_NONE:
					mTitleRight.setTextColor(Color.BLUE);
					mTitleRight.setText(R.string.title_not_connected);
					break;
				}
				break;
			case MESSAGE_READ:
				handleBluetoothReceivedMsg(msg);
				break;

			case MESSAGE_DEVICE_NAME:
				// save the connected device's name
				mConnectedDeviceName = msg.getData().getString(DEVICE_NAME);
				Toast.makeText(getApplicationContext(), "Connected to " + mConnectedDeviceName, Toast.LENGTH_SHORT).show();
				break;
			case MESSAGE_TOAST:
				Toast.makeText(getApplicationContext(), msg.getData().getString(TOAST),	Toast.LENGTH_SHORT).show();
				break;
			}
		}
	};

	protected void handleBluetoothReceivedMsg(Message msg)
	{
		byte[] readBuf = (byte[]) msg.obj;
		String readMessage = new String(readBuf, 0, msg.arg1);

		Log.d(TAG, "rec: "+readMessage);
		//mTitleLeft.setText(readMessage);

		if(readMessage.length()>=4 && currentLocation!=null)
		{
			StringTokenizer tokens= new StringTokenizer(readMessage," []");

			String s1=(String) tokens.nextElement();
			String s2=(String) tokens.nextElement();
			String s3=(String) tokens.nextElement();

			Log.i(TAG, "Received: " +s1+ " "+s2+ " "+ s3 );

			double lat = Double.parseDouble(s1);
			double lon = Double.parseDouble(s2);
			double or  = Double.parseDouble(s3);

			allOtherLocations.add(new LatLong(lat, lon, or));

			updateCenterText();
		}
	}

	@Override
	public synchronized void onPause() {
		super.onPause();

		Log.d(TAG, "- ON PAUSE -");

		mLocationManager.removeUpdates(mLocationListener);

		mSensorManager.unregisterListener(this);
	}

	@Override
	public void onStop() {
		super.onStop();

		Log.d(TAG, "-- ON STOP --");
	}

	@Override
	protected void onDestroy() {
		super.onDestroy();

		Log.d(TAG, "-- ON DESTROY --");

		if (mChatService != null) mChatService.stop();
	}

	/**
	 * Sends a message.
	 * @param message  A string of text to send.
	 */
	protected void sendMessage(String message) {
		// Check that we're actually connected before trying anything
		if (mChatService.getState() != BluetoothChatService.STATE_CONNECTED) {
			Toast.makeText(this, R.string.not_connected, Toast.LENGTH_SHORT).show();
			return;
		}

		// Check that there's actually something to send
		if (message.length() > 0) {
			// Get the message bytes and tell the BluetoothChatService to write
			byte[] send = message.getBytes();
			mChatService.write(send);
		}
	}

	public void onActivityResult(int requestCode, int resultCode, Intent data) {

		Log.d(TAG, "onActivityResult " + resultCode);

		switch (requestCode) {
		case REQUEST_CONNECT_DEVICE:
			// When DeviceListActivity returns with a device to connect
			if (resultCode == Activity.RESULT_OK) {
				// Get the device MAC address
				String address = data.getExtras().getString(DeviceListActivity.EXTRA_DEVICE_ADDRESS);
				// Get the BLuetoothDevice object
				BluetoothDevice device = mBluetoothAdapter.getRemoteDevice(address);
				// Attempt to connect to the device
				mChatService.connect(device);
			}
			break;
		case REQUEST_ENABLE_BT:
			// When the request to enable Bluetooth returns
			if (resultCode == Activity.RESULT_OK) {
				// Bluetooth is now enabled, so set up a chat session
				setupChat();
			} else {
				// User did not enable Bluetooth or an error occured
				Log.d(TAG, "BT not enabled");
				Toast.makeText(this, R.string.bt_not_enabled_leaving, Toast.LENGTH_SHORT).show();
				finish();
			}
			break;

		case RETURN_FROM_MAPS:

			if (spinningWheel!=null) spinningWheel.cancel();

			break;
		}
	}

	@Override
	public boolean onCreateOptionsMenu(Menu menu) {
		MenuInflater inflater = getMenuInflater();
		inflater.inflate(R.menu.option_menu, menu);
		return true;
	}

	@Override
	public boolean onOptionsItemSelected(MenuItem item) {
		switch (item.getItemId()) {
		case R.id.scan:
			// Launch the DeviceListActivity to see devices and do scan
			Intent serverIntent = new Intent(this, DeviceListActivity.class);
			startActivityForResult(serverIntent, REQUEST_CONNECT_DEVICE);
			return true;
		case R.id.discoverable:
			// Ensure this device is discoverable by others
			ensureDiscoverable();
			return true;
		}
		return false;
	}

	private void ensureDiscoverable() {

		Log.d(TAG, "ensure discoverable");

		if (mBluetoothAdapter.getScanMode() !=
			BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE) {
			Intent discoverableIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE);
			discoverableIntent.putExtra(BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION, 300);
			startActivity(discoverableIntent);
		}
	}

	@Override
	public boolean onKeyUp(int keyCode, KeyEvent event) {

		if(keyCode == KeyEvent.KEYCODE_FOCUS)
			hasBtnBeenReleased = true;

		return (super.onKeyUp(keyCode, event));
	}

	protected void processLocation() {
		if(hasBtnBeenReleased)
		{
			if(currentLocation!=null)
			{
				allMyLocations.add(new LatLong(currentLocation, currentOrientation));
				updateCenterText();

				Log.d(TAG, "our: lat: "+currentLocation.getLatitude()+" long: "+currentLocation.getLongitude());

				if(mChatService.getState() == BluetoothChatService.STATE_CONNECTED)
				{
					Log.d(TAG, "sending message!");
					sendMessage("["+currentLocation.getLatitude()+" "+currentLocation.getLongitude()+" "+currentOrientation+"]");
				}
				else
				{
					Log.w(TAG, "bluetooth not connected!");
				}
			}
			else
			{
				Log.w(TAG, "no location found yet");

				noConnection.cancel();
				noConnection.show();
			}

			hasBtnBeenReleased=false;
		}
	}
	
	@Override
	public boolean onKeyDown(int keyCode, KeyEvent event) {
		
		int currentZoom;
		
		switch(keyCode)
		{

		case KeyEvent.KEYCODE_BACK:
//			if(topPanel.getVisibility()==View.VISIBLE || bottomPanel.getVisibility()==View.VISIBLE) 
//			{
//				Log.d(TAG,"HERE");
//				topPanel.setVisibility(View.GONE);
//				bottomPanel.setVisibility(View.GONE);
//				return true;
//			}
			finish();
			break;

		//case KeyEvent.KEYCODE_CAMERA:
		case KeyEvent.KEYCODE_FOCUS: 
			processLocation();
			break;

		case KeyEvent.KEYCODE_VOLUME_UP:

			currentZoom = CameraSurfaceView.mCamera.getParameters().getZoom();
			
			if(currentZoom>0)
			{
				CameraSurfaceView.mCamera.startSmoothZoom(currentZoom-1);
			}

			return true;	

		case KeyEvent.KEYCODE_VOLUME_DOWN:

			currentZoom = CameraSurfaceView.mCamera.getParameters().getZoom();
			
			if(currentZoom < CameraSurfaceView.mCamera.getParameters().getMaxZoom())
			{
				CameraSurfaceView.mCamera.startSmoothZoom(currentZoom+1);
			}

			return true;

		default:
			Log.d(TAG, "Button pressed: "+keyCode);
		}

		return (super.onKeyDown(keyCode, event));
	}

	protected void updateCenterText()
	{
		mTitleCenter.setTextColor(Color.YELLOW);
		mTitleCenter.setText("My points: [" + allMyLocations.size() + 
				"]  Other points: [" + allOtherLocations.size() + "]");
	}

	public void startMaps(boolean rightEntrance)
	{
		spinningWheel.show();

		startActivityForResult(mapIntent, RETURN_FROM_MAPS);

		if(rightEntrance)
			overridePendingTransition  (R.anim.right_slide_in, 0);
		else
			overridePendingTransition  (R.anim.left_slide_in, 0);
	}
}

